export interface curlToJsonResult {
    url: string;
    method: string;
    headers: Record<string, string>;
    params: Record<string, any>;
    data: Record<string, any>;
}

export default {
    curlToJson,
}

/**
 * Attempt to parse the given curl string.
 */
export function curlToJson(s: string): curlToJsonResult {
    const res: curlToJsonResult = {
        url: '',
        method: '',
        headers: {},
        params: {},
        data: {},
    }
    if (0 != s.indexOf('curl ')) return res

    // 将字符串分割成参数数组
    // split("foo 'bar baz'");  =>   ["foo", "bar baz"]
    const args = rewrite(split(s))
    res.method = res.method || 'GET'
    let state = ''

    args.forEach(function (arg: any) {
        switch (true) {
            case isURL(arg):
                const url = new URL(arg)
                res.url = url.origin + url.pathname
                if (url.search) {
                    const params = new URLSearchParams(url.search)
                    res.params = { ...parseParamsField(params.toString()) }
                }
                break
            case arg === '--data-raw':
                res.method = 'POST'
                state = 'data'
                break
            case arg === '-A' || arg === '--user-agent':
                state = 'user-agent'
                break
            case arg === '--data-urlencode':
                state = 'data.urlencode'
                break
            case arg === '--data-binary':
                state = 'data.binary'
                break
            case arg === '-F' || arg === '--form':
                state = 'form'
                break
            case arg === '-H' || arg === '--header':
                state = 'header'
                break
            case arg === '-d' || arg === '--data' || arg === '--data-ascii' || arg === '--data-raw':
                state = 'data'
                break
            case arg === '-u' || arg === '--user':
                state = 'user'
                break
            case arg === '-I' || arg === '--head':
                res.method = 'HEAD'
                break
            case arg === '-X' || arg === '--request' || arg === '--location':
                state = 'method'
                break
            case arg === '-b' || arg === '--cookie':
                state = 'cookie'
                break
            case arg === '--compressed':
                res.headers['Accept-Encoding'] = res.headers['Accept-Encoding'] || 'deflate, gzip'
                break
            case !!arg:
                switch (state) {
                    case 'header':
                        const field = parseField(arg)
                        field[0] = (field[0] || '').split('-').map(item => item.charAt(0).toUpperCase() + item.slice(1)).join('-')
                        res.headers[field[0]] = field[1]
                        state = ''
                        break
                    case 'user-agent':
                        res.headers['User-Agent'] = arg
                        state = ''
                        break
                    case 'data':
                        res.headers['Content-Type'] = res.headers['Content-Type'] || 'application/x-www-form-urlencoded'
                        console.log('arg', arg)
                        res.data = { ...JSON.parse(arg) }
                        state = ''
                        break
                    case 'user':
                        res.headers['Authorization'] = 'Basic ' + btoa(arg)
                        state = ''
                        break
                    case 'method':
                        res.method = arg
                        state = ''
                        break
                    case 'cookie':
                        res.headers['Set-Cookie'] = arg
                        state = ''
                        break
                    // case 'form':
                    //     var field = parseFromField(arg)
                    //     out.form[field[0]] = field[1].substring(1, field[1].length - 1)
                    //     state = ''
                    //     break
                    // case 'data.binary':
                    //     out.binary_data = arg
                    //     break
                    // case 'data.urlencode':
                    //     var field = parseDataUrlencodeField(arg)
                    //     out.data_urlencode[field[0]] = field[1]
                    //     break
                }
                break
        }
    })

    return res
}

/**
 * Rewrite args for special cases such as -XPUT.
 */
function rewrite(args: any[]) {
    return args.reduce(function (args, a) {
        if (0 === a.indexOf('-X')) {
            args.push('-X')
            args.push(a.slice(2))
        } else {
            args.push(a)
        }

        return args
    }, [])
}

/**
 * Parse header field.
 */

function parseField(s: string) {
    return s.split(/: (.+)/)
}

/**
 * Parse params field.
 * @param {s: string} 
 * @returns object | null
 */
function parseDataUrlencodeField(s: string) {
    return s.split(/=/)
}

/**
 * Parse params field.
 * @param {s: string} 
 * @returns object | null
 */
function parseParamsField(s: string) {
    if (s === "") return null;
    let object: Record<string, any> = {}
    const allParamsArr = s.split(/&/)
    allParamsArr.forEach(element => {
        const field = element.split(/=/)
        object[field[0]] = field[1]
    })

    return object
}

/**
 * Parse params field.
 * @param {arg: string} 
 * @returns object | null
 */

function parseFromField(arg: string) {
    return arg.split(/=/)
}

/**
 * Check if `s` looks like a url.
 */
function isURL(s: string) {
    return /^https?:\/\//.test(s) || /^localhost?:/.test(s) || /^127.0.0.1?:/.test(s)
}

const scan = (
    string: string,
    pattern: RegExp,
    callback: (match: RegExpMatchArray) => void
) => {
    let result = "";

    while (string.length > 0) {
        const match = string.match(pattern);

        if (match && match.index != null && match[0] != null) {
            result += string.slice(0, match.index);
            result += callback(match);
            string = string.slice(match.index + match[0].length);
        } else {
            result += string;
            string = "";
        }
    }

    return result;
};

/**
 * Splits a string into an array of tokens in the same way the UNIX Bourne shell does.
 *
 * @param line A string to split.
 * @returns An array of the split tokens.
 */
export const split = (line: string = "") => {
    const words = [];
    let field = "";
    scan(
        line,
        /\s*(?:([^\s\\\'\"]+)|'((?:[^\'\\]|\\.)*)'|"((?:[^\"\\]|\\.)*)"|(\\.?)|(\S))(\s|$)?/,
        (match) => {
            const [_raw, word, sq, dq, escape, garbage, separator] = match;

            if (garbage != null) {
                throw new Error(`Unmatched quote: ${line}`);
            }

            if (word) {
                field += word;
            } else {
                let addition;

                if (sq) {
                    addition = sq;
                } else if (dq) {
                    addition = dq;
                } else if (escape) {
                    addition = escape;
                }

                if (addition) {
                    field += addition.replace(/\\(?=.)/, "");
                }
            }

            if (separator != null) {
                words.push(field);
                field = "";
            }
        }
    );

    if (field) {
        words.push(field);
    }

    return words;
}